Importamos los módulos necesarios para resolver esta tarea.
### Basicos
import numpy as np
import pandas as pd
from pandas import DataFrame
### Utilidades/Varios
import matplotlib.pyplot as plt
from matplotlib import colors as mcolors
from matplotlib.patches import Circle
from sklearn.tree import export_graphviz
from sklearn import tree
import seaborn as sns
import time
import graphviz
import os
### Training/Testing
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix
import warnings
warnings.filterwarnings('ignore')
Cree en Python usando numpy un tensor de 0D, 1D, 2D y uno 3D.
print("Un tensor de 0D:")
x0 = np.array(1)
print(x0)
print(f"Dim: {x0.ndim}")
print("Un tensor de 1D:")
x1 = np.array([1,1,2,3,5,8])
print(x1)
print(f"Dim: {x1.ndim}")
print("Un tensor de 2D:")
x2 = np.array([[1,1,2,3,5,8], [13,21,34,55,89,144]])
print(x2)
print(f"Dim: {x2.ndim}")
print("Un tensor de 3D:")
x3 = np.array([ [[1,1,2], [3,5,8], [13,21,34]], [[1,2,5],[14,42,132],[429,1430,4862]] ])
print(x3)
print(f"Dim: {x3.ndim}")
Un tensor de 0D: 1 Dim: 0 Un tensor de 1D: [1 1 2 3 5 8] Dim: 1 Un tensor de 2D: [[ 1 1 2 3 5 8] [ 13 21 34 55 89 144]] Dim: 2 Un tensor de 3D: [[[ 1 1 2] [ 3 5 8] [ 13 21 34]] [[ 1 2 5] [ 14 42 132] [ 429 1430 4862]]] Dim: 3
Imprima las dimensiones (shape) de la tabla de entrenamiento de MNIST. Esta es una tabla de dígitos escritos a mano y se usan para problemas de clasificación (problemas predictivos). Esta tabla viene con el paquete tensorflow y se obtiene como sigue:
from tensorflow.keras.datasets import mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz 11490434/11490434 [==============================] - 3s 0us/step
Las dimensiones de ambas tablas son las siguientes:
x_train.shape
(60000, 28, 28)
x_test.shape
(10000, 28, 28)
Como vemos, ambas son en realidad tensores (tienen 3 dimensiones).
¿Qué tipo de datos contiene MNIST?
x_test
array([[[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]],
[[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]],
[[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]],
...,
[[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]],
[[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]],
[[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
...,
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0],
[0, 0, 0, ..., 0, 0, 0]]], dtype=uint8)
x_test[0].shape
(28, 28)
Este conjunto de datos está formado por dos tensores de 3 dimensiones (uno para entrenamiento y otro para prueba). Cada elemento en un tensor se puede ver como una matriz, de tamaño $28 \times 28$. Cada matriz en realidad representa un dígito escrito a mano.
La operación relu() es una operación que se aplica entrada por entrada de un vector, esta devuelve el máximo entre cada entrada del vector y 0 (relu(x) = max(x,0)). Reprograme esta función en Python y después pruébela con el siguiente vector x.
x = np.array([1, -9, -0.9, 45])
def relu(x):
def relu_entrada(t):
return max(t,0)
# Usamos el método vectorize de numpy para aplicar una función entrada por entrada.
# Otras opciones y su velocidad de ejecución se discuten aquí:
# https://stackoverflow.com/questions/35215161/most-efficient-way-to-map-function-over-numpy-array
return np.vectorize(relu_entrada)(x)
relu(x)
array([ 1., 0., 0., 45.])
Calcule "a mano" el gradiente de la siguiente función:
$$ f(x, y, z) = 3x^4 - 6x\sin(yz) + 3y\cos(z). $$Respuesta: El gradiente que se solicita viene dado por lo siguiente: (lo he calculado a mano).
$$ \begin{align*} &\frac{\partial f}{\partial x} = 12x^3 - 6\sin(yz) \\\\ &\frac{\partial f}{\partial y} = -6xz\cos(yz) + 3\cos(z) \\\\ &\frac{\partial f}{\partial z} = -6xy\cos(yz) -3y\sin(z). \end{align*} $$Por lo tanto
$$ \nabla f(x,y,z) = (12x^3 - 6\sin(yz), -6xz\cos(yz) + 3\cos(z), -6xy\cos(yz) -3y\sin(z) ) $$ Usando el algoritmo del Descenso de Gradiente encuentre "a mano" (puede utilizar excel) un mínimo local de la siguiente función en el intervalo $[1, 4]$, use el punto de partida que considere adecuados. Luego grafique y verifique los resultados con código Python.
Respuesta:
Note que $f'(x) = 12x^3 - 48x^2 + 36x$, y en cada paso lo que tenemos que hacer es calcular
$$ x_{i+1} = x_i - \eta f'(x_i) \Rightarrow x_{i+1} = x_i - \eta (12x_i^3 - 48x_i^2 + 36x_i) $$Iniciaremos con $x_0 = 2.5$ (la mitad del intervalo) para ver qué sucede. Utilizaremos un $\eta$ de $0.005$
Los cálculos están en la hoja Pregunta6 del documento en excel: Tarea11_Jimmy_Calvo_Calculos.xlsx adjunto.
Ahora en código Python
figsize = (6,4)
dpi = 150
def f(x):
return 3*x**4 - 16*x**3 + 18*x**2
def df(x):
return 12*x**3 - 48*x**2 + 36*x
eta = 0.005
x = [2.5]
y = [f(x[0])]
for i in range(20):
x.append(x[i] - eta * df(x[i]))
y.append(f(x[i] - eta * df(x[i])))
dt = pd.DataFrame({"X": x, "Y": y})
print(dt)
t1 = np.arange(-1.0, 4.0, 0.1)
fig, ax = plt.subplots(1,1, figsize = figsize, dpi = dpi)
ax.set_xlim(-5, 5)
ax.set_ylim(-45, 45)
ax.plot(t1, f(t1))
ax.plot(x,y,'ro')
X Y 0 2.500000 -20.312500 1 2.612500 -22.690445 2 2.710444 -24.446122 3 2.790988 -25.604198 4 2.853675 -26.290483 5 2.900116 -26.660469 6 2.933141 -26.844994 7 2.955887 -26.931652 8 2.971189 -26.970594 9 2.981314 -26.987559 10 2.987936 -26.994796 11 2.992236 -26.997839 12 2.995013 -26.999107 13 2.996801 -26.999632 14 2.997949 -26.999849 15 2.998686 -26.999938 16 2.999159 -26.999975 17 2.999461 -26.999990 18 2.999655 -26.999996 19 2.999779 -26.999998 20 2.999859 -26.999999
[<matplotlib.lines.Line2D at 0x2577efa40d0>]
Con lo que vemos que hemos llegado al mínimo en $x=3$. He utilizado este $\eta$ ya que después de algunos intentos, éste fue el que me permitió llegar a un mínimo que estuviera en el intervalo. Como se observa en la figura anterior, hay otro mínimo de esta función en $x=0$, y con $\eta$ más grandes, estaba llegando a ese mínimo (ya vimos que el algoritmo del descenso del gradiente puede converger a mínimos locales, eso era lo que estaba pasando en este caso).
Usando el algoritmo del Descenso de Gradiente encuentre "a mano" (puede utilizar excel) el mínimo global de la siguiente función, use un punto de partida que considere adecuado. Luego grafique y verifique los resultados con código Python.
En este caso, nuestro gradiente es igual a
$$ \begin{align*} &\frac{\partial f}{\partial x} = 4x^3 -4x + 4y\\ &\frac{\partial f}{\partial y} = 4y^3 + 4x -4y.\\ \end{align*} $$Vamos a iniciar con el punto $(x_0,y_0)=(0.5,1.5)$, y utilizaremos un $\eta = 0.01$. Los resultados del cálculo en excel se encuentran en la hoja Pregunta7 del documento Tarea11_Jimmy_Calvo_Calculos.xlsx adjunto.
Ahora, procedemos a revisar estos cálculos con Python.
import plotly.graph_objs as go
import plotly.express as px
# Import the necessaries libraries
import plotly.offline as pyo
import plotly.graph_objs as go
# Set notebook mode to work in offline
pyo.init_notebook_mode()
def f7(x,y):
return x**4 + y**4 -2*x**2 + 4*x*y - 2*y**2
def gradiente_f7(x,y):
return np.array([4*x**3 - 4*x + 4*y, 4*y**3 + 4*x - 4*y])
x = 0.5
y = 1.5
eta = 0.01
xi = np.array([x,y])
x = []
y = []
z = []
for i in range(60):
xi = xi - eta * gradiente_f7(xi[0],xi[1])
x.append(xi[0])
y.append(xi[1])
z.append(f7(xi[0],xi[1]))
dataF = pd.DataFrame({"X":x, "Y": y, "Z": z})
print(dataF)
#Gráfico
fig = px.scatter_3d(dataF, x='X', y='Y', z='Z',color_discrete_sequence=["red"])
xdata = np.arange(-2, 2, 0.1)
ydata = np.arange(-2, 2, 0.1)
X,Y = np.meshgrid(xdata, ydata)
Z = X**4 + Y**4 -2*X**2 + 4*X*Y - 2*Y**2
fig.add_trace(go.Surface(
x = X,
y = Y,
z = Z,
opacity = .7, showscale = False,
colorscale='Viridis'
))
fig.show()
X Y Z 0 0.455000 1.405000 2.134634 1 0.413232 1.332060 1.489107 2 0.373656 1.274270 1.033891 3 0.335545 1.227530 0.691938 4 0.298355 1.189222 0.420734 5 0.261658 1.157582 0.194917 6 0.225104 1.131373 -0.001667 7 0.188397 1.109697 -0.179913 8 0.151278 1.091889 -0.347584 9 0.113515 1.077442 -0.510500 10 0.074899 1.065968 -0.673254 11 0.035239 1.057161 -0.839640 12 -0.005639 1.050779 -1.012921 13 -0.047896 1.046628 -1.195993 14 -0.091672 1.044548 -1.391466 15 -0.137090 1.044410 -1.601706 16 -0.184247 1.046100 -1.828809 17 -0.233211 1.049523 -2.074557 18 -0.284013 1.054591 -2.340311 19 -0.336641 1.061220 -2.626888 20 -0.391029 1.069329 -2.934402 21 -0.447052 1.078834 -3.262091 22 -0.504514 1.089644 -3.608151 23 -0.563143 1.101660 -3.969610 24 -0.622592 1.114770 -4.342270 25 -0.682433 1.128851 -4.720758 26 -0.742172 1.143763 -5.098727 27 -0.801257 1.159350 -5.469194 28 -0.859105 1.175443 -5.825033 29 -0.915124 1.191862 -6.159533 30 -0.968748 1.208418 -6.466974 31 -1.019469 1.224920 -6.743097 32 -1.066863 1.241179 -6.985405 33 -1.110612 1.257018 -7.193226 34 -1.150522 1.272275 -7.367569 35 -1.186516 1.286811 -7.510787 36 -1.218633 1.300511 -7.626157 37 -1.247009 1.313293 -7.717446 38 -1.271855 1.325102 -7.788525 39 -1.293439 1.335911 -7.843080 40 -1.312056 1.345719 -7.884431 41 -1.328020 1.354548 -7.915434 42 -1.341637 1.362438 -7.938462 43 -1.353202 1.369441 -7.955431 44 -1.362991 1.375618 -7.967851 45 -1.371252 1.381038 -7.976888 46 -1.378207 1.385769 -7.983433 47 -1.384052 1.389881 -7.988154 48 -1.388958 1.393441 -7.991547 49 -1.393071 1.396513 -7.993978 50 -1.396516 1.399154 -7.995717 51 -1.399400 1.401420 -7.996957 52 -1.401814 1.403358 -7.997841 53 -1.403834 1.405013 -7.998469 54 -1.405523 1.406424 -7.998915 55 -1.406937 1.407624 -7.999232 56 -1.408120 1.408643 -7.999456 57 -1.409109 1.409509 -7.999616 58 -1.409938 1.410242 -7.999728 59 -1.410631 1.410862 -7.999808
Vemos que hay una convergencia hacia el punto $(-\sqrt{2},\sqrt{2})$ que es uno de los mínimos locales de esta función.
Usando el algoritmo del Descenso de Gradiente encuentre "a mano" (puede utilizar excel) un mínimo local de la siguiente función, use el punto de partida que considere adecuados. Luego grafique y verifique los resultados con código Python.
En este caso, nuestro gradiente es igual a
$$ \begin{align*} &\frac{\partial f}{\partial x} = 3x^2 - 3 \\ &\frac{\partial f}{\partial y} = -3y^2 + 3.\\ \end{align*} $$Vamos a iniciar con el punto $(x_0,y_0)=(0.8,0.2)$, y utilizaremos un $\eta = 0.01$. Los resultados del cálculo en excel se encuentran en la hoja Pregunta8 del documento Tarea11_Jimmy_Calvo_Calculos.xlsx adjunto.
Seleccioné este punto después de ver el gráfico de la función y adivinar que sería un punto en el cual el descenso del gradiente podría funcionar bien.
Ahora, procedemos a revisar estos cálculos con Python.
def f8(x,y):
return x**3 + 3*y - y**3 - 3*x
def gradiente_f8(x,y):
return np.array([3*x**2 - 3, -3*y**2 + 3])
x = 0.8
y = 0.2
eta = 0.01
xi = np.array([x,y])
x = []
y = []
z = []
for i in range(60):
xi = xi - eta * gradiente_f8(xi[0],xi[1])
x.append(xi[0])
y.append(xi[1])
z.append(f8(xi[0],xi[1]))
dataF = pd.DataFrame({"X":x, "Y": y, "Z": z})
print(dataF)
#Gráfico
fig = px.scatter_3d(dataF, x='X', y='Y', z='Z',color_discrete_sequence=["red"])
xdata = np.arange(-2, 2, 0.1)
ydata = np.arange(-2, 2, 0.1)
X,Y = np.meshgrid(xdata, ydata)
Z = X**3 + 3*Y - Y**3 - 3*X
fig.add_trace(go.Surface(
x = X,
y = Y,
z = Z,
opacity = .7, showscale = False,
colorscale='Viridis'
))
fig.show()
X Y Z 0 0.810800 0.171200 -1.390801 1 0.821078 0.142079 -1.486319 2 0.830853 0.112685 -1.582384 3 0.840144 0.083066 -1.678798 4 0.848968 0.053273 -1.775346 5 0.857346 0.023358 -1.871791 6 0.865295 -0.006626 -1.967884 7 0.872833 -0.036624 -2.063366 8 0.879977 -0.066584 -2.157970 9 0.886747 -0.096451 -2.251430 10 0.893157 -0.126172 -2.343481 11 0.899225 -0.155694 -2.433866 12 0.904967 -0.184967 -2.522338 13 0.910398 -0.213941 -2.608664 14 0.915533 -0.242568 -2.692629 15 0.920387 -0.270803 -2.774039 16 0.924974 -0.298603 -2.852719 17 0.929307 -0.325928 -2.928521 18 0.933398 -0.352741 -3.001320 19 0.937261 -0.379008 -3.071019 20 0.940908 -0.404699 -3.137544 21 0.944348 -0.429785 -3.200849 22 0.947595 -0.454244 -3.260909 23 0.950656 -0.478054 -3.317724 24 0.953544 -0.501198 -3.371318 25 0.956267 -0.523662 -3.421731 26 0.958833 -0.545435 -3.469024 27 0.961252 -0.566510 -3.513272 28 0.963532 -0.586882 -3.554565 29 0.965680 -0.606549 -3.593004 30 0.967704 -0.625512 -3.628699 31 0.969611 -0.643774 -3.661771 32 0.971406 -0.661341 -3.692341 33 0.973097 -0.678220 -3.720538 34 0.974690 -0.694420 -3.746492 35 0.976189 -0.709953 -3.770332 36 0.977601 -0.724832 -3.792189 37 0.978930 -0.739071 -3.812191 38 0.980181 -0.752684 -3.830461 39 0.981358 -0.765688 -3.847122 40 0.982466 -0.778100 -3.862290 41 0.983509 -0.789937 -3.876078 42 0.984490 -0.801217 -3.888593 43 0.985414 -0.811958 -3.899935 44 0.986282 -0.822180 -3.910201 45 0.987100 -0.831901 -3.919481 46 0.987869 -0.841139 -3.927859 47 0.988592 -0.849913 -3.935414 48 0.989273 -0.858243 -3.942219 49 0.989913 -0.866145 -3.948343 50 0.990515 -0.873639 -3.953847 51 0.991082 -0.880742 -3.958791 52 0.991614 -0.887471 -3.963226 53 0.992115 -0.893842 -3.967202 54 0.992587 -0.899874 -3.970764 55 0.993030 -0.905581 -3.973951 56 0.993446 -0.910978 -3.976802 57 0.993838 -0.916082 -3.979351 58 0.994207 -0.920906 -3.981627 59 0.994554 -0.925464 -3.983658
Con estos cálculos y los realizados en excel, parece que la convergencia es hacia el mínimo que se encuentra en el punto $(1,-1)$.
¿Qué pasa si en el código visto en clase para optimizar una Función de Costo (gradient_descent) se usa un momentum (impulso) más pequeño a $0.7$? ¿Qué pasa si en el código visto en clase para optimizar una Función de Costo (gradient descent) se usa un $\eta$ (learning_rate) de $10^{-9}$?
def gradient_descent(max_iterations,threshold,w_init,
obj_func,grad_func,extra_param = [],
learning_rate=0.05,momentum=0.8):
w = w_init
w_history = w
f_history = obj_func(w,extra_param)
delta_w = np.zeros(w.shape)
i = 0
diff = 1.0e10
while i<max_iterations and diff>threshold:
delta_w = -learning_rate*grad_func(w,extra_param) + momentum*delta_w
w = w+delta_w
# Almacena la historia of w y f
w_history = np.vstack((w_history,w))
f_history = np.vstack((f_history,obj_func(w,extra_param)))
i+=1
diff = np.absolute(f_history[-1]-f_history[-2])
return w_history,f_history
# w es un vector de pesos y xy el vetor fila de datos (train_data, target)
def grad_mse(w,xy):
(x,y) = xy
(rows,cols) = x.shape
# Calcula la salida
o = np.sum(x*w,axis=1)
diff = y-o
diff = diff.reshape((rows,1))
diff = np.tile(diff, (1, cols))
grad = diff*x
grad = -np.sum(grad,axis=0)
return grad
# w es un vector de pesos y xy el vector fila de datos (train_data, target)
def mse(w,xy):
(x,y) = xy
# Calcula la salida usando el mse
o = np.sum(x*w,axis=1)
mse = np.sum((y-o)*(y-o))
mse = mse/2
return mse
Vamos a leer los datos utilizando la biblioteca de datasets de sklearn.
from sklearn import datasets
digits = datasets.load_digits()
datos = pd.DataFrame(digits['data'])
datos.columns = digits['feature_names']
datos['digito'] = digits['target']
datos = datos[datos['digito'].isin([0,1])]
print(datos.shape)
datos.head(5)
(360, 65)
| pixel_0_0 | pixel_0_1 | pixel_0_2 | pixel_0_3 | pixel_0_4 | pixel_0_5 | pixel_0_6 | pixel_0_7 | pixel_1_0 | pixel_1_1 | ... | pixel_6_7 | pixel_7_0 | pixel_7_1 | pixel_7_2 | pixel_7_3 | pixel_7_4 | pixel_7_5 | pixel_7_6 | pixel_7_7 | digito | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0.0 | 0.0 | 5.0 | 13.0 | 9.0 | 1.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 6.0 | 13.0 | 10.0 | 0.0 | 0.0 | 0.0 | 0 |
| 1 | 0.0 | 0.0 | 0.0 | 12.0 | 13.0 | 5.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 11.0 | 16.0 | 10.0 | 0.0 | 0.0 | 1 |
| 10 | 0.0 | 0.0 | 1.0 | 9.0 | 15.0 | 11.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 1.0 | 10.0 | 13.0 | 3.0 | 0.0 | 0.0 | 0 |
| 11 | 0.0 | 0.0 | 0.0 | 0.0 | 14.0 | 13.0 | 1.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 0.0 | 1.0 | 13.0 | 16.0 | 1.0 | 0.0 | 1 |
| 20 | 0.0 | 0.0 | 3.0 | 13.0 | 11.0 | 7.0 | 0.0 | 0.0 | 0.0 | 0.0 | ... | 0.0 | 0.0 | 0.0 | 2.0 | 12.0 | 13.0 | 4.0 | 0.0 | 0.0 | 0 |
5 rows × 65 columns
Ahora vamos a aplicar la función con los parámetros por defecto, como vimos en clase.
Y = datos["digito"].ravel()
X = datos.drop(columns=["digito"]).to_numpy()
# Separa train y test set
x_train, x_test, y_train, y_test = train_test_split(
X, Y, test_size=0.2, random_state=10)
# Agrega la columna de unos de al regresión (bias) en train and test
x_train = np.hstack((np.ones((y_train.size,1)),x_train))
x_test = np.hstack((np.ones((y_test.size,1)),x_test))
# Inicializa los pesos y llama a gradient_descent
rand = np.random.RandomState(19)
w_init = rand.uniform(-1,1,x_train.shape[1])*.000001
w_history,mse_history = gradient_descent(100,0.1,w_init,
mse,grad_mse,(x_train,y_train),
learning_rate=1e-6,momentum=0.7)
# Grafica el MSE
fig, ax = plt.subplots(1,1, figsize = figsize, dpi = dpi)
ax.plot(np.arange(mse_history.size),mse_history)
ax.set_xlabel('Número de Iteración')
ax.set_ylabel('Error Cuadrático Medio')
ax.set_title('Descenso del gradiente para la función de costo - Ejemplo de Dígitos')
Text(0.5, 1.0, 'Descenso del gradiente para la función de costo - Ejemplo de Dígitos')
Esto es lo que obtuvimos en clase. Pero ahora qué pasa si tomamos $\eta <0.7$, por ejemplo $0.2$. Veamos.
w_history,mse_history = gradient_descent(100,0.1,w_init,
mse,grad_mse,(x_train,y_train),
learning_rate=1e-6,momentum=0.2)
# Grafica el MSE
fig, ax = plt.subplots(1,1, figsize = figsize, dpi = dpi)
ax.plot(np.arange(mse_history.size),mse_history)
ax.set_xlabel('Número de Iteración')
ax.set_ylabel('Error Cuadrático Medio')
ax.set_title('Descenso del gradiente para la función de costo - Ejemplo de Dígitos')
Text(0.5, 1.0, 'Descenso del gradiente para la función de costo - Ejemplo de Dígitos')
En este caso vemos que hay una convergencia un poco más rápida hacia un error cuadrático medio de $\epsilon=0$. Si hacemos el learning rate más pequeño, sucederá lo que sigue.
w_history,mse_history = gradient_descent(100,0.1,w_init,
mse,grad_mse,(x_train,y_train),
learning_rate=1e-8,momentum=0.7)
# Grafica el MSE
fig, ax = plt.subplots(1,1, figsize = figsize, dpi = dpi)
ax.plot(np.arange(mse_history.size),mse_history)
ax.set_xlabel('Número de Iteración')
ax.set_ylabel('Error Cuadrático Medio')
ax.set_title('Descenso del gradiente para la función de costo - Ejemplo de Dígitos')
Text(0.5, 1.0, 'Descenso del gradiente para la función de costo - Ejemplo de Dígitos')
Al disminuir mucho la taza de aprendizaje, se puede ver que se necesitará una cantidad mayor de iteraciones para disminuir el error cuadrádico medio lo suficiente. Y esto tiene sentido, ya que si nuestra taza de aprendizaje es muy pequeña, entonces no habrá mucha diferencia de un paso a otro en el algoritmo del descenso del gradiente.
En este repositorio de github, Lili Jiang creó una aplicación en C++ que permite crear animaciones para visualizar el proceso del descenso del gradiente: gradient_descent_viz.